import UndoType from './UndoType.js';
import {createContourLineSegments} from "../../lib/util/StaticMethods.js";
import {updateSlicingSegment} from "../EditOrientation.js";
import RoutePointVO from "../../model/RoutePointVO.js";

const debugShapeExtract = false
const tmpRoutePoint = new RoutePointVO()

export default class EditAction {
  constructor(undoType, context) {
    this.undoType = undoType;
    this.context = context;
    const index = UndoType.list.indexOf(this.undoType);
    if (index >= 0) {
      let redoIndex = index + 1;
      if (index % 2 === 1) {
        redoIndex = index - 1;
      }
      this.redoType = UndoType.list[redoIndex];
    } else {
      console.error('undo error! invalid undo type: ' + undoType);
    }
  }

  undo() {
    cmdExecutor.execute(this.undoType, this.context);
  }

  redo() {
    cmdExecutor.execute(this.redoType, this.context);
  }
}

function removeItem(arr, item) {
  let ind = arr.indexOf(item);
  if (ind > -1) {
    arr.splice(ind, 1);
  }
  return ind
}

// 集合A - 集合B，arrA 会被修改，arrB不改变
function arrAsubB(arrA, arrB) {
  arrB.forEach((it) => {
    const ind = arrA.indexOf(it);
    if (ind >= 0) {
      arrA.splice(ind, 1);
    }
  });
  return arrA;
}

class CommandExecutor {
  execute(undoType, obj) {
    switch (undoType) {
      case UndoType.addEye: {
        const { arr, indicator } = obj;
        removeItem(arr, indicator);
        indicator.removeFromParent();
        break;
      }
      case UndoType.removeEye: {
        const { scene, arr, indicator } = obj;
        arr.push(indicator);
        scene.add(indicator);
        break;
      }
      case UndoType.addSampleCube: {
        const { cube, transformControl } = obj;
        transformControl.detach();
        cube.removeFromParent();
        break;
      }
      case UndoType.removeSampleCube: {
        const { scene, cube, transformControl } = obj;
        scene.add(cube);
        transformControl.attach(cube);
        break;
      }

      case UndoType.updateEye: {
        const { pos, focus, indicator } = obj;
        indicator.position.copy(pos);
        indicator.setFocus(focus);
        break;
      }
      case UndoType.updateEyeUndo: {
        const { posNew, focusNew, indicator } = obj;
        indicator.position.copy(posNew);
        indicator.setFocus(focusNew);
        break;
      }
      case UndoType.addWorkArea: {
        const { arr, added, removed, scene } = obj;
        added.forEach((mesh) => {
          removeItem(arr, mesh);
          mesh.removeFromParent();
        });
        removed.forEach((areaMesh) => {
          scene.add(areaMesh);
          arr.push(areaMesh);
        });
        if (obj.callback) {
          obj.callback(removed);
        }
        break;
      }
      case UndoType.removeWorkArea: {
        const { arr, scene, added, removed } = obj;
        added.forEach((areaMesh) => {
          scene.add(areaMesh);
          arr.push(areaMesh);
        });
        removed.forEach((mesh) => {
          removeItem(arr, mesh);
          mesh.removeFromParent();
        });
        if (obj.callback) {
          obj.callback(added);
        }
        break;
      }
      case UndoType.removeHole:
      case UndoType.addHole: {
        let { scene, addedArea, removedArea, arrArea, arrHole, holeMesh } = obj;
        if (undoType === UndoType.removeHole) {
          arrHole.push(holeMesh);
          scene.add(holeMesh);
        } else {
          removeItem(arrHole, holeMesh);
          holeMesh.removeFromParent();
        }
        addedArea.forEach((mesh) => {
          mesh.removeFromParent();
          const ind = arrArea.indexOf(mesh);
          if (ind >= 0) {
            arrArea.splice(ind, 1);
          }
        });
        removedArea.forEach((mesh) => {
          scene.add(mesh);
          arrArea.push(mesh);
        });
        obj.removedArea = addedArea;
        obj.addedArea = removedArea;
        break;
      }
      case UndoType.replaceHoleUndo:
      case UndoType.replaceHole: {
        // 替换孔洞
        let {
          scene,
          addedArea,
          removedArea,
          arrArea,
          arrHole,
          oldHole,
          newHole,
        } = obj;
        if (undoType === UndoType.replaceHoleUndo) {
          oldHole = obj.newHole;
          newHole = obj.oldHole;
          addedArea = obj.removedArea;
          removedArea = obj.addedArea;
        }
        obj.callback(oldHole);
        const isHole = oldHole.vo.isHole
        const index = isHole ? arrHole.indexOf(newHole) : arrArea.indexOf(newHole)
        if (index >= 0) {
          if (isHole) {
            arrHole[index] = oldHole;
          } else {
            arrArea[index] = oldHole;
          }
          newHole.parent.add(oldHole);
          newHole.removeFromParent();
        }
        if (isHole) {
          addedArea.forEach((mesh) => mesh.removeFromParent());
          arrAsubB(arrArea, addedArea)
          removedArea.forEach((mesh) => {
            scene.add(mesh);
            arrArea.push(mesh);
          });
        }

        break;
      }
      case UndoType.shapeHoleUndo:
      case UndoType.shapeHole: {
        // 孔洞发生形变
        let {
          scene,
          addedArea,
          removedArea,
          arrArea,
          holeMesh,
          index,
          oldPos,
          newPos,
          posAttr,
          adherePos,
        } = obj;
        if (undoType === UndoType.shapeHoleUndo) {
          oldPos = newPos;
          addedArea = removedArea;
          removedArea = obj.addedArea;
        }
        holeMesh.updateShapeByVertDragging(index, oldPos);
        holeMesh.updateModelData();
        posAttr.setXYZ(index, oldPos.x, oldPos.y, oldPos.z)
        posAttr.needsUpdate = true
        adherePos.copy(oldPos)

        addedArea.forEach((mesh) => mesh.removeFromParent());
        arrAsubB(arrArea, addedArea)
        removedArea.forEach((mesh) => {
          scene.add(mesh);
          arrArea.push(mesh);
        });
        break;
      }
      case UndoType.setWorkAreaColor: {
        const { colorRef, colorOld } = obj;
        colorRef.setStyle(colorOld);
        break;
      }
      case UndoType.unsetWorkAreaColor: {
        const { colorRef, colorNew } = obj;
        colorRef.setStyle(colorNew);
        break;
      }
      case UndoType.addSprayPathPoint: {
        const {
          addedX,
          addedY,
          xLines,
          yLines,
          pathPoints,
          addedPoint,
          addedPointMesh,
          sprayedMesh,
        } = obj;
        arrAsubB(xLines, addedX);
        arrAsubB(yLines, addedY);
        removeItem(pathPoints, addedPoint);
        addedPointMesh.removeFromParent();
        sprayedMesh.removeFromParent();
        if (obj.addedSegment) {
          const segment = obj.addedSegment;
          segment.removeFromParent();
        }
        break;
      }
      case UndoType.addSprayPathPointUndo: {
        const {
          addedX,
          addedY,
          xLines,
          yLines,
          pathPoints,
          addedPoint,
          addedPointMesh,
          sprayedMesh,
          holder,
        } = obj;
        xLines.push(...addedX);
        yLines.push(...addedY);
        pathPoints.push(addedPoint);
        holder.add(addedPointMesh);
        holder.add(sprayedMesh);
        if (obj.addedSegment) {
          const segment = obj.addedSegment;
          holder.add(segment);
        }
        break;
      }
      case UndoType.transformBlocking: {
        const blockingMesh = obj.blockingMesh;
        if (obj.position0) {
          blockingMesh.position.copy(obj.position0);
        }
        if (obj.scale0) {
          blockingMesh.scale.copy(obj.scale0);
          if (obj.scaleControl && obj.scaleControl.object) {
            const sObj = obj.scaleControl.object;
            obj.scaleControl.detach();
            obj.scaleControl.attach(sObj);
          }
        }
        if (obj.rotation0) {
          blockingMesh.rotation.copy(obj.rotation0);
        }
        break;
      }
      case UndoType.unTransformBlocking: {
        const blockingMesh = obj.blockingMesh;
        if (obj.position) {
          blockingMesh.position.copy(obj.position);
        }
        if (obj.scale) {
          blockingMesh.scale.copy(obj.scale);
          if (obj.scaleControl && obj.scaleControl.object) {
            const sObj = obj.scaleControl.object;
            obj.scaleControl.detach();
            obj.scaleControl.attach(sObj);
          }
        }
        if (obj.rotation) {
          blockingMesh.rotation.copy(obj.rotation);
        }
        break;
      }
      case UndoType.addBlocking: {
        const { arr, blockingMesh } = obj;
        removeItem(arr, blockingMesh);
        blockingMesh.removeFromParent();
        break;
      }
      case UndoType.removeBlocking: {
        const { scene, arr, blockingMesh } = obj;
        arr.push(blockingMesh);
        scene.add(blockingMesh);
        break;
      }
      case UndoType.addPlaneRefPoint: {
        const { planeRefSpheres, sphereMesh, planeRefMesh, eyePos } = obj;
        sphereMesh.removeFromParent();
        planeRefSpheres.pop();
        const points = planeRefSpheres.map((it) => it.position);
        if (points.length >= 4) {
          planeRefMesh.updateBySamplePoints(points, eyePos);
        }
        break;
      }
      case UndoType.removePlaneRefPoint: {
        const { scene, planeRefSpheres, sphereMesh, planeRefMesh, eyePos } =
          obj;
        scene.add(sphereMesh);
        planeRefSpheres.push(sphereMesh);
        const points = planeRefSpheres.map((it) => it.position);
        if (points.length >= 4) {
          planeRefMesh.updateBySamplePoints(points, eyePos);
        }
        break;
      }
      case UndoType.addSliceSegment: {
        const { segmentSlice, store, sphere, sphereArr, scene, segments, shapeExtract} = obj;
        segmentSlice.removeFromParent();
        if (store.stateAddingSliceLine) {
          sphere.removeFromParent();
          sphereArr.pop();
          updateSlicingSegment(scene, sphere.position, false);
          segments.pop();
          if (!debugShapeExtract) {
            shapeExtract.sliceUndo()
          }
        }
        break;
      }
      case UndoType.removeSliceSegment: {
        const { lineStart, lineEnd, shapeExtract, store, scene, sphere,sphereArr,segments,cameraPosition, cameraRotation} = obj;
        const linePoints = shapeExtract.computeIntersectSegments(lineStart, lineEnd, cameraPosition, cameraRotation, true);
        if (linePoints.length > 1) {
          if (debugShapeExtract) {
            obj.segmentSlice = createContourLineSegments(linePoints, '#0b7');
          }
          scene.add(obj.segmentSlice);
          if (store.stateAddingSliceLine) {
            sphereArr.push(sphere);
            scene.add(sphere);
            updateSlicingSegment(scene, sphere.position, false);
            segments.push(obj.segmentSlice);
            if (!debugShapeExtract) {
              shapeExtract.sliceRedo()
            }
          }
        }
        break;
      }
      case UndoType.addRoutePoint: {
        const {routePointIndicator, arr, sequence, route} = obj
        routePointIndicator.removeFromParent()
        removeItem(arr, routePointIndicator)
        routePointIndicator.disposeLine()
        const idx = removeItem(sequence, routePointIndicator)
        obj.idx = idx
        const pointNext = sequence[idx]
        const pointPrev = sequence[idx - 1]
        if (pointNext) {
          if (pointPrev) {
            pointNext.updateLine(pointPrev.vo)
          } else {
            pointNext.disposeLine()
          }
        }
        if (!sequence.length) {
          removeItem(obj.routeArr, route)
        }
        break;
      }
      case UndoType.removeRoutePoint: {
        const {routePointIndicator, arr, sequence, scene, idx, routeArr, route} = obj
        scene.add(routePointIndicator)
        arr.push(routePointIndicator)
        sequence.splice(idx, 0, routePointIndicator)
        if (sequence.length === 1) {
          if (!routeArr.includes(route)) {
            routeArr.push(route)
          }
        } else {
          const pointNext = sequence[idx + 1]
          const pointPrev = sequence[idx - 1]
          if (pointNext) {
            pointNext.restoreLine(scene, routePointIndicator.vo)
          }
          if (pointPrev) {
            routePointIndicator.restoreLine(scene, pointPrev.vo)
          }
        }
        break;
      }
      case UndoType.moveRoutePoint:
      case UndoType.unMoveRoutePoint: {
        const {routePointIndicator, oldRoutePointVO, routeVO} = obj
        tmpRoutePoint.copy(routePointIndicator.vo)
        routePointIndicator.vo.copy(oldRoutePointVO)
        oldRoutePointVO.copy(tmpRoutePoint)
        routePointIndicator.syncToVO()
        const idx = routeVO.pointMeshes.indexOf(routePointIndicator)
        const pointNext = routeVO.pointMeshes[idx + 1]
        const pointPrev = routeVO.pointMeshes[idx - 1]
        if (pointNext) {
          pointNext.updateLine(routePointIndicator.vo)
        }
        if (pointPrev) {
          routePointIndicator.updateLine(pointPrev.vo)
        }
        break;
      }
      case UndoType.addRoute: {
        const {route, routeArr, routePoints} = obj
        arrAsubB(routePoints, route.pointMeshes)
        removeItem(routeArr, route)
        route.pointMeshes.forEach(it => {
          it.disposeLine()
          it.removeFromParent()
        })
        break;
      }
      case UndoType.removeRoute: {
        const {scene, route, routeArr, routePoints} = obj
        routeArr.push(route)
        route.pointMeshes.forEach(it => {
          routePoints.push(it)
          scene.add(it)
          it.unDisposeLine(scene)
        })
        break;
      }
      default:
        break;
    }
  }
}

const cmdExecutor = new CommandExecutor();
